package com.codefish.framework.spring.beans.factory;

import com.codefish.framework.spring.beans.exception.InstantiateBeanException;
import com.codefish.framework.spring.beans.exception.SetBeanPropertyException;
import com.codefish.framework.spring.beans.factory.config.IAutowireCapableBeanFactory;
import com.codefish.framework.spring.beans.factory.config.IBeanPostProcessor;
import com.codefish.framework.spring.beans.factory.config.IInstantiationAwareBeanPostProcessor;
import com.codefish.framework.spring.beans.factory.support.BeanDefinition;
import com.codefish.framework.spring.beans.factory.support.BeanReference;
import com.codefish.framework.spring.beans.factory.support.PropertyValue;
import com.codefish.framework.spring.core.utils.ClassUtils;
import com.codefish.framework.spring.core.utils.StringUtils;
import com.sun.istack.internal.Nullable;
import java.lang.ClassLoader;

import java.lang.reflect.Constructor;
import java.util.List;

/**
 * DefaultListableBeanFactory的抽象类，表示该BeanFactory支持往bean实例中注入属性值
 *
 * @author <a href='https://gitee.com/hzf2281'>codefish</a>
 * @version 1.0.0
 * @since 2022/09/29 下午 01:33
 */
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements IAutowireCapableBeanFactory {


    private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, @Nullable Object... args) {
        Class<?> beanClass = beanDefinition.getBeanClass();
        //尝试创建代理对象
        Object proxyBeanInstance = applyBeanPostProcessorsBeforeInstantiation(beanClass, beanName);
        if (proxyBeanInstance != null) {
            applyBeanPostProcessorsAfterInitialization(proxyBeanInstance, beanName);
            return proxyBeanInstance;
        }
        //代理对象仍为null，使用默认构造创建bean实例
        return doCreateBean(beanName, beanDefinition, args);
    }

    protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, @Nullable Object... args) {
        //实例化bean
        Object bean = getWrappedInstance(beanName, beanDefinition, args);
        //初始化bean
        Object exposedBean = bean;
        exposedBean = initializeBean(beanName, exposedBean, beanDefinition);
        //注册disposableBean
        registerDisposableBeanIfNecessary(beanName, exposedBean, beanDefinition);
        return exposedBean;
    }

    /**
     * 创建bean实例并设置属性
     *
     * @param beanName       目标beanName
     * @param beanDefinition 目标bd
     * @param args           构造函数入参
     * @return 创建的bean实例
     */
    protected Object getWrappedInstance(String beanName, BeanDefinition beanDefinition, @Nullable Object... args) {
        //bean实例化
        Object newInstance = createBeanInstance(beanDefinition, beanName, args);
        //bean设置属性
        populateBean(newInstance, beanDefinition, beanName);
        //返回bean实例对象
        return newInstance;
    }

    /**
     * 选择合适的构造方法并通过构造方法创建bean实例
     *
     * @param beanDefinition 目标bd
     * @param beanName       目标beanName
     * @param args           构造创建bean实例传入的参数
     * @return 对应的bean实例
     * @throws InstantiateBeanException 构造出错时抛出
     */
    protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, @Nullable Object... args) throws InstantiateBeanException {
        //从beanDefinition获取bean的class
        Class<?> beanClass = beanDefinition.getBeanClass();
        //根据入参找到对应构造方法
        Class<?>[] argsType = ClassUtils.getClassFromObjects(args);
        try {
            Constructor<?> beanConstructor = beanClass.getDeclaredConstructor(argsType);
            return beanConstructor.newInstance(args);
        } catch (Exception e) {
            e.printStackTrace();
            throw new InstantiateBeanException(beanName, beanDefinition);
        }
    }

    /**
     * 为bean设置属性
     *
     * @param bean           bean实例对象
     * @param beanDefinition 对应的bd
     * @param beanName       对应的beanName
     */
    protected void populateBean(Object bean, BeanDefinition beanDefinition, String beanName) {
        //设置属性之前，先执行postProcessAfterInstantiation方法
        for (IBeanPostProcessor processor : getBeanPostProcessorList()) {
            if (processor instanceof IInstantiationAwareBeanPostProcessor) {
                if (!((IInstantiationAwareBeanPostProcessor) processor).postProcessAfterInstantiation(bean, beanName)) {
                    //如果执行postProcessAfterInstantiation返回false，
                    // 那么后续的属性赋值就不需要执行下去了
                    return;
                }
            }
        }
        //获取propertyValues
        List<PropertyValue> propertyValues = beanDefinition.getPropertyValues();
        //对propertyValues进行处理
        for (IBeanPostProcessor processor : getBeanPostProcessorList()) {
            if (processor instanceof IInstantiationAwareBeanPostProcessor) {
                List<PropertyValue> result = ((IInstantiationAwareBeanPostProcessor) processor).postProcessPropertyValues(propertyValues, bean, beanName);
                if (result == null) {
                    break;
                }
                propertyValues = result;
            }
        }
        //将propertyValues赋值到bean实例上
        for (PropertyValue propertyValue : propertyValues) {
            final String fieldName = propertyValue.getName();
            final Object value = propertyValue.getValue();
            Object targetValue = value;
            try {
                if (value instanceof BeanReference) {
                    //如果value是BeanReference的实例，则说明依赖于一个bean对象
                    BeanReference br = (BeanReference) value;
                    String requiredBeanName = br.getBeanName();
                    //获取beanName对应的bean实例
                    targetValue = getBean(requiredBeanName);
                }
                //反射注入属性
                ClassUtils.setProperty(bean, fieldName, targetValue);
            } catch (Exception e) {
                e.printStackTrace();
                if (value instanceof BeanReference) {
                    throw new SetBeanPropertyException(beanName, fieldName, (BeanReference) value);
                } else {
                    throw new SetBeanPropertyException(beanName, fieldName, value);
                }
            }
        }
    }

    /**
     * 初始化bean
     */
    protected Object initializeBean(String beanName, Object bean, @Nullable BeanDefinition bd) {
        //执行bean的aware方法
        invokeAwareMethods(beanName, bean);
        Object wrappedBean = bean;
        //执行BeanPostProcessors的BeforeInitialization
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        //执行bean的初始化方法
        invokeInitMethods(beanName, bean, bd);
        //执行BeanPostProcessors的AAfterInitialization
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

        return wrappedBean;
    }

    /**
     * 执行bean的aware方法
     */
    protected void invokeAwareMethods(String beanName, Object bean) {
        if (bean instanceof IAware) {
            if (bean instanceof IBeanNameAware) {
                ((IBeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof IBeanClassLoaderAware) {
                ((IBeanClassLoaderAware) bean).setBeanClassLoader(this.beanClassLoader);
            }
            if (bean instanceof IBeanFactoryAware) {
                ((IBeanFactoryAware) bean).setBeanFactory(this);
            }
        }
    }

    /**
     * 执行bean的初始化方法
     */
    protected void invokeInitMethods(String beanName, Object bean, BeanDefinition bd) {
        if (bean instanceof IInitializingBean) {
            //如果bean实现了InitializingBean接口，那么调用其afterPropertiesSet()方法
            try {
                ((IInitializingBean) bean).afterPropertiesSet();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //调用自定义配置的init-method
        invokeCustomInitMethod(beanName, bean, bd);
    }

    /**
     * bean自定义的inti-method，在xml中配置init-method如下：
     * <pre>
     * &lt;bean id="XXX" init-method='doXXX'>
     *         ....
     * &lt;/bean>
     * </pre>
     */
    protected void invokeCustomInitMethod(String beanName, Object bean, BeanDefinition bd) {
        String initMethodName = bd.getInitMethod();
        if (!StringUtils.isEmpty(initMethodName)) {
            try {
                ClassUtils.invokeMethod(bean, initMethodName, Void.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    @Override
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) {
        Object result = existingBean;
        for (IBeanPostProcessor beanPostProcessor : getBeanPostProcessorList()) {
            Object current = beanPostProcessor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    @Override
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) {
        Object result = existingBean;
        for (IBeanPostProcessor beanPostProcessor : getBeanPostProcessorList()) {
            Object current = beanPostProcessor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
        for (IBeanPostProcessor beanPostProcessor : getBeanPostProcessorList()) {
            if (beanPostProcessor instanceof IInstantiationAwareBeanPostProcessor) {
                Object result = ((IInstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

    protected void applyBeanPostProcessorsAfterInstantiation(Object bean, String beanName) {
        for (IBeanPostProcessor beanPostProcessor : getBeanPostProcessorList()) {
            if (beanPostProcessor instanceof IInstantiationAwareBeanPostProcessor) {
                boolean next = ((IInstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessAfterInstantiation(bean, beanName);
                if (!next) {
                    return;
                }
            }
        }
    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }
}
